/********************************************************************/
/*************** 为Base*.vue正常执行逻辑提供的常用的方法 ***************/
/********************************************************************/

import { emptyVals } from "@/consts";
import { isDev } from "@/consts";
import { BaseDataType, CommonObj, StrNum } from "@/types";
import _ from "lodash";

const { merge, cloneDeep } = _;

/**
 * 函数未传必填参数时的校验
 * @param {string} name 参数名称
 */
export function needParam(name: string = ""): any {
  throw new Error("请传入参数：" + name);
}

/**
 * 检测元素所属类型
 * @param {any} ele 要检测的元素
 * Object.prototype.toString.call(*)的可能结果如下所示：
 * @example null             [object Null]
 * @example undefined        [object Undefined]
 * @example Symbol());       [object Symbol]
 * @example true             [object Boolean]
 * @example ''               [object String]
 * @example 1                [object Number]
 * @example []               [object Array]
 * @example {}               [object Object]
 * @example new Date()       [object Date]
 * @example new Function()   [object Function]
 * @example new RegExp()     [object RegExp]
 * @example new Error()      [object Error]
 * @example document         [object HTMLDocument]
 * @example window           [object global] window 是全局对象 global 的引用
 * @param {*} ele 待检测的对象、字符串、数组等
 * @returns {String} 元素类型（string、number、boolean、symbol、undefined、null、function、date、array、object、regexp、error、htmldocument、global）
 */
export type TypeOfReturn =
  | "Null"
  | "Undefined"
  | "Symbol"
  | "Boolean"
  | "String"
  | "Number"
  | "Array"
  | "Object"
  | "PointerEvent"
  | "Date"
  | "Function"
  | "Promise"
  | "AsyncFunction"
  | "RegExp"
  | "Error"
  | "HTMLDocument"
  | "global";
export const typeOf = (ele: any): TypeOfReturn => {
  const endStr = Object.prototype.toString.call(ele);
  return endStr.split(" ")[1].slice(0, -1) as TypeOfReturn;
};

/**
 * 通用克隆方法
 * @param {any} data 要克隆的数据
 * @returns
 * @notice JSON.parse(JSON.stringify()) 会删除函数的键值对；structuredClone会有兼容性问题；lodash的cloneDeep的缺点待完善
 */
// export const commonClone = (data: any) => {
//   if (typeof data !== "object" || data === null) throw new Error("基础数据类型请不要使用此方法进行克隆");
//   return cloneDeep(data);
// };
export const commonClone = (data: any) => {
  // 如果为基础数据类型，function，null，则直接返回
  if (typeof data !== "object" || data === null) return data;
  let newData: any;
  const t = typeOf(data);
  if (t === "Array") {
    newData = [];
    data.forEach((item: any, ind: number) => {
      newData[ind] = commonClone(item);
    });
  } else if (t === "Object") {
    newData = {};
    Object.keys(data).forEach((key) => {
      newData[key] = commonClone(data[key]);
    });
  } else if (t === "Function") {
    throw new Error("检测到了类型为Function");
  } else {
    throw new Error(`暂未处理此种类型：${t}`);
  }
  return newData;
};

/**
 * 将字符串转为驼峰
 * @param {string} str 要转换的字符串
 * @param {boolean} isBig 是否转成大驼峰，否则转成小驼峰
 */
export function toCamelCase(str: string, isBig = false) {
  str = str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, ind) => {
    if (ind === 0) return letter[isBig ? "toUpperCase" : "toLowerCase"]();
    return letter.toUpperCase();
  });
  str = str.replace(/\s+|\/|-|}/g, "");
  return str;
}

/**
 * 获取中文字符长度（全角符算1个，半角符算0.5个字符）
 * @param {string} str 要处理的字符串
 */
export function getChinaCharLength(str?: string): number {
  if (!str) return 0;
  return str.replace(/[^\x00-\xff]/g, "**").length / 2;
}

/**
 * 获取字符串的字节长度（全角符算2个，半角符算1个字符）
 * @param {string} str 字符串
 */
export function getBytes(str = "") {
  const len = str.length;
  let bytes = len;
  for (let i = 0; i < len; i++) {
    if (str.charCodeAt(i) > 255) bytes++;
  }
  return bytes;
}

/**
 * 剔除对象值为指定类型的对象属性（后续考虑要不要剔除空数组、空对象）
 * @param {object} obj 要剔除属性的对象
 * @param {BaseDataType[]} list 要剔除的属性值构成的数组
 */
export function omitAttrs(
  obj: CommonObj,
  list: BaseDataType | BaseDataType[] = emptyVals
) {
  if (!list) return obj;
  if (list === true) list = emptyVals;
  if (!Array.isArray(list)) throw new Error(`list参数应该为一个数组`);
  const newObj: CommonObj = {};
  for (const key in obj) {
    const val = obj[key];
    const t = typeOf(val);
    if (t === "Object") {
      newObj[key] = omitAttrs(val, list);
      continue;
    }
    // 如果range类字段，每个值都为空字符串，则剔除掉这个属性
    if (t === "Array") {
      if (val.some((it: StrNum) => !list.includes(it))) newObj[key] = val;
      continue;
    }
    if (!list.includes(val)) {
      newObj[key] = val;
      continue;
    }
  }
  return newObj;
}

/**
 * 剔除对象属性（不会改变原数组）
 * @param {object} obj 要剔除属性的对象
 * @param {string | string[]} keys 剔除的属性数组
 */
export function deleteAttrs(
  obj: CommonObj = {},
  keys?: string | string[],
  clone = true
) {
  if (!keys?.length) return obj;
  const newObj = clone ? cloneDeep(obj) : obj;
  if (typeof keys === "string") return delete newObj[keys];
  keys.forEach((key) => delete newObj[key]);
  return newObj;
}

/**
 * 处理暴露的数据对象（对于系统内置的对象数据，采用的合并方式）
 * 合并策略
 * false：不进行合并；
 * merge：lodash 的 merge 方法进行合并，深度合并；
 * assign：JS原生的Object.assign合并，浅合并；
 * alert：有自定义值就完全采用，没用自定义值就采用系统默认；
 * auto：自动采用合并类型
 *@param {object} sysData 系统数据
 *@param {object} customData 自定义数据
 */
export type ConfigMergeStrategy = false | "merge" | "assign" | "alert" | "auto";
export function getExportData(
  sysData: any,
  customData?: any,
  mergeType: ConfigMergeStrategy = "assign"
) {
  if ([null, undefined].includes(customData)) return sysData;
  if (!mergeType) return customData;
  const isBaseData = ["string", "number", "boolean", "undefined"].includes(
    typeof sysData
  ); //如果是基础数据类型
  if (isBaseData) mergeType = "alert";
  if (mergeType === "merge") return merge(sysData, customData);
  if (mergeType === "assign") return Object.assign(sysData, customData);
  if (mergeType === "alert") return customData ?? sysData;
  if (mergeType === "auto") return sysData;
  throw new Error(`暂不支持${mergeType}合并策略`);
}

/**
 * 判断两个元素是否一致（是否修改过）
 * @param {any} e_1 第一个元素
 * @param {any} e_2 第二个元素
 */
export function getIsUpdated(e_1: any, e_2: any) {
  const t1 = typeOf(e_1);
  const t2 = typeOf(e_2);
  if (t1 !== t2) return true;
  if (t1 === "Array") {
    if (e_1.length !== e_2.length) return true;
    const updated = !!e_1.find((it: any, i: number) =>
      getIsUpdated(it, e_2[i])
    );
    if (updated) return true;
  } else if (t1 === "Object") {
    if (Object.keys(e_1).length !== Object.keys(e_2).length) return true;
    for (const key in e_1) {
      const updated = getIsUpdated(e_1[key], e_2[key]);
      if (updated) return true;
    }
  } else {
    if (e_1 !== e_2) return true;
  }
  return false;
}

/**
 * 数组排序（支持对象，数字数组等，默认order）
 * @param {CommonObj[]} arr 要排序的数组
 * @param {asc | desc | random} type 排序方式 asc 升序 desc 降序 random 随机
 * @param {string} key  排序依据的对象属性键名
 */
export type SortType = "asc" | "desc" | "random";
export function sortObjArrByKey(
  arr: CommonObj[] = [],
  type: SortType = "asc",
  key = "order"
): CommonObj[] {
  if (!arr?.length || !key) return arr;
  const keys = key.split(".");
  arr.sort((a: CommonObj, b: CommonObj) => {
    a?.children?.length && sortObjArrByKey(a.children, type, key);
    let aVal: any = a;
    let bVal: any = b;
    keys.find((k) => {
      if (typeof aVal !== "object" || typeof bVal !== "object") return true;
      aVal = aVal[k] ?? 0; // Infinity;
      bVal = bVal[k] ?? 0; // Infinity;
    });
    if (type === "asc") return aVal - bVal;
    if (type === "desc") return bVal - aVal;
    if (type === "random") return Math.random() - 0.5;
    throw new Error(`传参类型错误：${type}`);
  });
  return arr;
}

/**
 * 给数字每隔 3 位就增加一个逗号
 * @param {string | number} num 要转化的数字
 * @returns {string}
 */
export const addCommasToNumber = (num: string | number): string =>
  num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");

/**
 * 抛出错误信息（主要用在template模板中）
 * @param {string} msg 脱误提示信息
 */
export function throwTplError(msg: string) {
  throw new Error(msg);
}

/**
 * 是否是数组元素的删除
 */
// export function isArrDel(fObj: CommonObj, tObj: CommonObj) {
//   for (const key in fObj) {
//     const type = typeOf(fObj[key]);
//     if (type === "Array") {
//       const isDel = fObj[key].length > tObj[key]?.length;
//       if (isDel) return true;
//     } else if (type === "Object") {
//       const isDel = isArrDel(fObj[key], tObj[key]);
//       if (isDel) return true;
//     }
//   }
//   return false;
// }

/**
 * 获取完整的静态资源url
 * @tips import.meta.url 是一个 ESM 的原生功能，会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用，在一个 JavaScript 模块中，通过相对路径我们就能得到一个被完整解析的静态资源 URL：
 * @notice 注：无法在 SSR 中使用，因为 import.meta.url 在浏览器和 Node.js 中有不同的语义
 * @param {string} url 路径url
 * @returns string
 */
export function getWholeUrl(url = "./img.png") {
  // return new URL(`./dir/${name}.png`, import.meta.url).href; // 支持字符串模板
  return new URL(url, import.meta.url).href;
}

/**
 * 冻结对象，只可读，不可写入
 * @param obj 要处理的对象或数组
 * @returns
 */
export function deepFreeze(obj: CommonObj) {
  const propNames = Object.getOwnPropertyNames(obj);
  propNames.forEach((item) => {
    const prop = obj[item];
    if (prop instanceof Object && prop !== null) {
      deepFreeze(prop);
    }
  });
  return Object.freeze(obj);
}

/**
 * 深度克隆
 * @param {any} target
 * @param map
 * @returns {any}
 */
export function deepClone(target: any, map = new WeakMap()) {
  // 如果是基本类型或 null，直接返回
  if (target === null || typeof target !== "object") return target;
  // 如果是日期对象，返回一个新的日期对象
  if (target instanceof Date) return new Date(target);
  // 如果是正则表达式对象，返回一个新的正则表达式对象
  if (target instanceof RegExp) return new RegExp(target);
  // 如果是 Map 对象，递归克隆其键值对
  if (target instanceof Map) {
    const cloneMap = new Map();
    target.forEach((value, key) => cloneMap.set(key, deepClone(value, map)));
    return cloneMap;
  }
  // 如果是 Set 对象，递归克隆其值
  if (target instanceof Set) {
    const cloneSet = new Set();
    target.forEach((value) => {
      cloneSet.add(deepClone(value, map));
    });
    return cloneSet;
  }
  // 如果是数组或普通对象，递归克隆其属性
  const cloneTarget: any = Array.isArray(target) ? [] : {};
  // 使用 WeakMap 解决循环引用问题
  if (map.has(target)) return map.get(target);
  map.set(target, cloneTarget);
  for (const key in target) {
    if (target.hasOwnProperty(key)) {
      cloneTarget[key] = deepClone(target[key], map);
    }
  }
  return cloneTarget;
}

/**
 * 计算百分比
 * @param {number} num 当前值
 * @param {number} total 值总数
 * @param {number} fixedNum 要保留的小数位数
 */
export function getPercentage(
  num: number,
  total: number,
  fixedNum: number = 2
) {
  if (!total) return Number(0).toFixed(fixedNum);
  return ((num / total) * 100).toFixed(fixedNum);
}

/**
 * 检查对象2的属性在对象1上是否存在，即以对象1的属性为准
 * @param {object} obj_1 要检查的对象1
 * @param {object} obj_2 要检查的对象2
 */
export function checkObjKeyError(obj_1: CommonObj = {}, obj_2: CommonObj = {}) {
  if (!isDev) return;
  for (const key in obj_2) {
    if (typeof obj_1[key] === "undefined") {
      const msg = `属性名 ${key} 不存在目标对象上`;
      console.error(msg, obj_1);
    }
  }
}
